﻿// <copyright file="Retry.cs" company="N/A">
//     Copyright (c) Nicholas Barrett and Benjamin Kasten.  All rights reserved.
//
//     This program is free software; you can redistribute it and/or modify it
//     under the terms of the GNU General Public License as published by the
//     Free Software Foundation; either version 2 of the License, or (at your
//     option) any later version.
//
//     This program is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
//     Public License for more details.
//
//     You should have received a copy of the GNU General Public License along
//     with this program; if not, write to the Free Software Foundation, 
//     Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// </copyright>

namespace BetterAzureTableStorage
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using Microsoft.WindowsAzure.StorageClient;

    /// <summary>
    /// Contains methods to invoke actions until they execute without exception.
    /// </summary>
    public static class Retry
    {
        /// <summary>
        /// Executes the <paramref name="action"/> until it completes without exception or
        /// until the delegate invoked by RetryPolicy returns false.
        /// </summary>
        /// <param name="action">The action to invoke.</param>
        /// <param name="retryPolicy">The retry policy.</param>
        public static void Invoke(Action action, RetryPolicy retryPolicy)
        {
            int retryCount = 0;
            Exception lastException = null;
            TimeSpan delay = TimeSpan.FromMilliseconds(5000);

            while (retryCount == 0 || retryPolicy()(retryCount, lastException, out delay))
            {
                try
                {
                    action();
                    return;
                }
                catch (Exception ex)
                {
                    retryCount++;
                    lastException = ex;
                    Thread.Sleep(delay.Milliseconds);
                }
            }

            throw new TRetryException(string.Format("Operation failed after {0} retries.", retryCount), lastException);
        }
    }

    /// <summary>
    /// Exception thrown when a Retry fails after the maximum number of attempts.
    /// </summary>
    /// <remarks>
    /// InnerException contains the exception thrown by the final retry attempt.
    /// </remarks>
    [Serializable]
    public class TRetryException : Exception
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="TRetryException"/> class.
        /// </summary>
        public TRetryException()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TRetryException"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        public TRetryException(string message)
            : base(message)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TRetryException"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="innerException">The inner exception.</param>
        public TRetryException(string message, Exception innerException)
            : base(message, innerException)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TRetryException"/> class.
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="info"/> parameter is null. </exception>
        /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult"/> is zero (0). </exception>
        [ExcludeFromCodeCoverage]
        protected TRetryException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
        }
    }
}
